package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// 数据库模型（与PostgreSQL表结构严格对应）
type User struct {
	gorm.Model
	Name  string `json:"name"`
	Email string `json:"email"`
	Bio   string `json:"bio"`
}

type Project struct {
	gorm.Model
	ID          uint   `gorm:"primaryKey"`
	Title       string `json:"title"`
	Description string `json:"description"`
	Tags        []Tag  `gorm:"many2many:project_tags;"`
	Image       string `json:"image"`
	Link        string `json:"link"`
}

type Tag struct {
	gorm.Model
	Name string `json:"name"`
}

var globalDB *gorm.DB // 全局数据库连接实例

// 连接数据库
func connectDB() error {
	dsn := "host=localhost user=postgres password=1 dbname=personal_website port=5432 sslmode=disable TimeZone=Asia/Shanghai"
	var db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		return err
	}

	// 添加表结构自动迁移
	if err := db.AutoMigrate(&User{}, &Project{}); err != nil {
		return fmt.Errorf("表结构迁移失败: %v", err)
	}

	// 将 db 赋值给全局变量（如果需要）
	globalDB = db
	return nil
}

// 初始化基础数据（从mock_data迁移）
func initData() error {
	// 仅在表为空时插入数据
	var count int64
	globalDB.Model(&Project{}).Count(&count)
	if count == 0 {
		// 读取mock数据
		var mockData MockData
		file, _ := os.Open("config/mock_data.json")
		defer file.Close()
		json.NewDecoder(file).Decode(&mockData)

		// 插入数据（需处理事务）
		return globalDB.Transaction(func(tx *gorm.DB) error {
			if err := tx.Create(&mockData.Users).Error; err != nil {
				return err
			}
			return tx.Create(&mockData.Projects).Error
		})
	}
	return nil
}

func main() {

	// 连接数据库并执行迁移
	if err := connectDB(); err != nil {
		// 原代码修改为使用 log 包
		log.Fatalf("数据库初始化失败: %v", err)
	}

	r := gin.Default()

	// 添加CORS中间件
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://localhost:3000"},
		AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Accept"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}))

	// 健康检查接口
	r.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status":  "ok",
			"version": "1.0.0",
		})
	})

	// 用户信息接口（改为数据库查询）
	r.GET("/api/user/:id", func(c *gin.Context) {
		var user User
		if err := globalDB.Where("id = ?", c.Param("id")).First(&user).Error; err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
			return
		}
		c.JSON(http.StatusOK, user)
	})

	// 项目列表接口（改为数据库查询）
	r.GET("/api/projects", func(c *gin.Context) {
		var projects []Project
		// 预加载标签数据
		if err := globalDB.Preload("Tags").Find(&projects).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "查询项目失败: " + err.Error()})
			return
		}

		// 格式化响应数据以匹配mock_data.json格式
		type ProjectResponse struct {
			ID          uint     `json:"id"`
			Title       string   `json:"title"`
			Description string   `json:"description"`
			Tags        []string `json:"tags"`
			Image       string   `json:"image"`
			Link        string   `json:"link,omitempty"`
		}

		var response []ProjectResponse
		for _, p := range projects {
			tags := make([]string, len(p.Tags))
			for i, tag := range p.Tags {
				tags[i] = tag.Name
			}
			response = append(response, ProjectResponse{
				ID:          p.ID,
				Title:       p.Title,
				Description: p.Description,
				Tags:        tags,
				Image:       p.Image,
				Link:        p.Link,
			})
		}

		c.JSON(http.StatusOK, response)
	})

	// 处理未匹配的路由
	r.NoRoute(func(c *gin.Context) {
		file := filepath.Join("./frontend-dist", c.Request.URL.Path)
		c.File(file)
	})

	// 启动服务
	r.Run(":8080")
}

type MockData struct {
	Users    []User    `json:"users"`
	Projects []Project `json:"projects"`
}

var mockData MockData

// 加载配置文件
func loadMockData() error {
	filePath := "cmd/config/mock_data.json" // 修正为相对于项目根目录的路径
	// 或使用相对于 main.go 的相对路径（若从 cmd 目录运行程序）
	// filePath := "./config/mock_data.json"
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&mockData); err != nil {
		return err
	}

	return nil
}
